#include <fstream>
#include <windows.h>

#include "libpan.hpp"

using namespace std;

int main(int argc, char **argv)
{
	Print("\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB\n");
	Print("\xBA                                                                             \xBA\n");
	Print("\xBA                                    P A N                                    \xBA\n");
	Print("\xBA                                    \xCD\xCD\xCD\xCD\xCD                                    \xBA\n");
	Print("\xBA                                                                             \xBA\n");
	Print("\xBA                                 GUNZ CLIENT                                 \xBA\n");
	Print("\xBA                                                                             \xBA\n");
	Print("\xC8\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC\n\n");
	Print("Pan: Created by Snail\n");
	Print("Pan: Last updated on %s at %s\n", __DATE__, __TIME__);
	Print("Pan: http://pasteum.com/pan\n");

	char buf[SIZE_1K];

	memset(buf, 0, sizeof(buf));
	GetProgramDirectory(buf, sizeof(buf));

	Print("Pan: CWD: %s\n", buf);

	_snprintf(buf, sizeof(buf), "%s\\pan.ini", buf);
	ifstream fin(buf, ios::in);

	if(fin.is_open())
	{
		char ini_contents[SIZE_64K];

		memset(ini_contents, 0, sizeof(ini_contents));
		SetINIContents(ini_contents);

		fin.read(ini_contents, sizeof(ini_contents));
		fin.close();

		char server[SIZE_1K];

		memset(server, 0, sizeof(server));
		ExtractFromINI(GetINIContents(), "[Pan]", "Server = ", server);

		char port_buf[SIZE_1K];

		memset(port_buf, 0, sizeof(port_buf));
		ExtractFromINI(GetINIContents(), "[Pan]", "Port = ", port_buf);
		unsigned short port = atoi(port_buf);
	
		SetServerIP(server);
		SetServerPort(port);

		Print("Pan: Loaded config\n");

		memset(buf, 0, sizeof(buf));
		GetProgramDirectory(buf, sizeof(buf));
		_snprintf(buf, sizeof(buf), "%s\\plugins\\*.dll", buf);

		unsigned long plugin;
		unsigned long plugin_count = 0;
		unsigned long plugins[SIZE_1K];

		WIN32_FIND_DATA file;

		void *find = FindFirstFile(buf, &file);

		if(find != INVALID_HANDLE_VALUE)
		{
			do
			{
				memset(buf, 0, sizeof(buf));
				GetProgramDirectory(buf, sizeof(buf));
				_snprintf(buf, sizeof(buf), "%s\\plugins\\%s", buf, file.cFileName);

				if(plugin = reinterpret_cast<unsigned long>(LoadLibrary(buf)))
				{
					plugins[plugin_count] = plugin;
					plugin_count++;

					Print("Pan: Loaded plugin: %s\n", file.cFileName);
				}
				else
					Print("Pan: Could not load plugin: %s (%d)\n", file.cFileName, GetLastError());
			} while(FindNextFile(find, &file));
		}
		else
			Print("Pan: Could not find any plugins\n");

		typedef void (*Init)();
		Init init;

		for(int i = 0; i < (int)plugin_count; i++)
		{
			if(GetProcAddress(reinterpret_cast<HMODULE>(plugins[i]), "Init"))
			{
				init = reinterpret_cast<Init>(GetProcAddress(reinterpret_cast<HMODULE>(plugins[i]), "Init"));
				init();
			}
		}

		WS2 *ws2 = new WS2();
		TCPSocket *match = new TCPSocket();
		SetMatchSocket(match);

		for(int i = 0; i < 5; i++)
		{
			if(match->Connect(GetServerIP(), GetServerPort()) < 0)
			{
				Print("Pan: Could not connect to: %s:%d (%d)\n", GetServerIP(), GetServerPort(), match->GetLastError());
			
				if(i < 4)
					Print("Pan: Retrying...\n");
			}
			else
			{
				Print("Pan: Connected to: %s:%d\n", GetServerIP(), GetServerPort());
				SetConnected(true);

				break;
			}
		}

		if(IsConnected())
		{
			unsigned char key[] =
			{
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x37, 0x04, 0x5D, 0x2E, 0x43, GUNZPACKET_COMMAND_VERSION, 0x49, 0x53,
				0x50, 0x05, 0x13, 0xC9, 0x28, 0xA4, 0x4D, 0x05
			};

			SetKey(key);

			typedef void (*Callback)(GunZPacket *callback_packet);
			Callback callback;

			unsigned char received[SIZE_64K];
			memset(received, 0, sizeof(received));

			int received_size;

			while((received_size = match->Receive(reinterpret_cast<char *>(received), sizeof(received))) != SOCKET_ERROR)
			{
				GunZPacket *packet = new GunZPacket();
				packet->FillPacket(received, received_size);

				if(packet->GetType() == GUNZPACKET_ENCRYPTED)
				{
					packet->Decrypt(key);
					packet->SetType(GUNZPACKET_DECRYPTED);
					packet->CalculateChecksum(packet->GetPacketSize());
				}
				else if(packet->GetType() == GUNZPACKET_KEY_EXCHANGE)
				{
					memcpy(key, &packet->GetPacket()[22], 4);
					memcpy(&key[4], &packet->GetPacket()[10], 12);

					unsigned char xor_table[] =
					{
						0x57, 0x02, 0x5B, 0x04, 0x34, 0x06, 0x01, 0x08,
						0x37, 0x0A, 0x12, 0x69, 0x41, 0x38, 0x0F, 0x78
					};

					for(unsigned int i = 0; i < sizeof(key) / 2; i++)
						key[i] ^= xor_table[i];
				
					SetKey(key);
				}

				if(packet->GetCommandID())
				{
					char message[SIZE_1K];
					char name[SIZE_1K];
					char *padding;

					int result;
					int victories;

					unsigned int junk;
					unsigned long id;
					unsigned short message_size;
					unsigned short name_size;

					switch(packet->GetCommandID())
					{
						case CHANNEL_CHAT:
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&name_size), sizeof(name_size));

							memset(name, 0, sizeof(name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(name), name_size);

							memset(message, 0, sizeof(message));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&message_size), sizeof(message_size));
							packet->GetBytes(reinterpret_cast<unsigned char *>(message), message_size);

							Print("Pan: [Channel] <%s> %s\n", name, message);

							break;

						case CHANNEL_LEAVE:					
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
									
							SetInChannel(false);
							SetChannelID(0);
							SetChannelName(0);
							SetChannelRule(0);
							SetChannelType(0);
							
							Print("Pan: Left channel: %s\n", GetChannelName());
							
							break;
					
						case CHANNEL_RESPONSE_RULE:							
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&id), sizeof(id));
					
							unsigned short channel_rule_size;
							packet->GetBytes(reinterpret_cast<unsigned char *>(&channel_rule_size), sizeof(channel_rule_size));

							char channel_rule[SIZE_1K];

							memset(channel_rule, 0, sizeof(channel_rule));
							packet->GetBytes(reinterpret_cast<unsigned char *>(channel_rule), channel_rule_size);
							SetChannelRule(channel_rule);
										
							break;
					
						case CHANNEL_RESPONSEJOIN:
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&id), sizeof(id));

							SetChannelID(id);
							int type;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&type), sizeof(type));	
							SetChannelType(type);
					
							unsigned short channel_name_size;
							packet->GetBytes(reinterpret_cast<unsigned char *>(&channel_name_size), sizeof(channel_name_size));

							char channel_name[SIZE_1K];

							memset(channel_name, 0, sizeof(channel_name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(channel_name), channel_name_size);
							SetChannelName(channel_name);
							SetInChannel(true);
							
							Print("Pan: Joined channel: %s\n", GetChannelName());
					
							break;

						case CHANNEL_RESPONSEPLAYERLIST:
							break;

						case CLOCK_SYNCHRONIZE:
							break;

						case MATCH_BROADCAST_DUELINTERRUPTVICTORIES:
							memset(name, 0, sizeof(name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&name_size), sizeof(name_size));
							packet->GetBytes(reinterpret_cast<unsigned char *>(name), name_size);

							unsigned short interrupter_size;
							packet->GetBytes(reinterpret_cast<unsigned char *>(&interrupter_size), sizeof(interrupter_size));

							char interrupter[SIZE_1K];

							memset(interrupter, 0, sizeof(name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(interrupter), interrupter_size);
							packet->GetBytes(reinterpret_cast<unsigned char *>(&victories), sizeof(victories));

							Print("Pan: %s has stopped %s's winning streak of %d\n", interrupter, name, victories);

							break;

						case MATCH_BROADCAST_DUELRENEWVICTORIES:
							memset(name, 0, sizeof(name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&name_size), sizeof(name_size));
							packet->GetBytes(reinterpret_cast<unsigned char *>(name), name_size);

							Print("Pan: %s is currently on a winning streak of ", name);

							memset(name, 0, sizeof(name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&name_size), sizeof(name_size));
							packet->GetBytes(reinterpret_cast<unsigned char *>(name), name_size);

							int room;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&room), sizeof(room));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&victories), sizeof(victories));

							Print("%d in room %d\n", victories, room);

							break;

						case MATCH_RESPONSEACCOUNTCHARLIST:						
							unsigned int encrypted_characters;
				
							packet->GetBytes(reinterpret_cast<unsigned char *>(&encrypted_characters), sizeof(encrypted_characters));
							encrypted_characters = (encrypted_characters - 8) / 34;
							
							unsigned int characters;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&characters), sizeof(characters));

							if(characters == encrypted_characters)
								Print("Pan: Found %d character(s)\n", characters);

							else
								Print("Pan: Received a corrupted Match.ResponseAccountCharList packet\n");

							break;

						case MATCH_RESPONSECREATECHAR:
							packet->GetBytes(reinterpret_cast<unsigned char *>(&result), sizeof(result));

							switch(result)
							{
								case RESPONSE_NAME_EMPTY:							
									Print("Pan: Failed to create character (must supply a name)\n");

									break;

								case RESPONSE_NAME_IN_USE:							
									Print("Pan: Failed to create character (name is already in use)\n");

									break;

								case RESPONSE_NAME_INVALID_CHARACTERS:								
									Print("Pan: Failed to create character (name contains invalid characters)\n");

									break;

								case RESPONSE_NAME_TOO_LONG:							
									Print("Pan: Failed to create character (name is too long)\n");

									break;

								case RESPONSE_NAME_TOO_SHORT:							
									Print("Pan: Failed to create character (name is too short)\n");

									break;

								case RESPONSE_OK:								
									unsigned short buf_size;
									packet->GetBytes(reinterpret_cast<unsigned char *>(&buf_size), sizeof(buf_size));

									char buf[SIZE_1K];

									memset(buf, 0, sizeof(buf));
									packet->GetBytes(reinterpret_cast<unsigned char *>(buf), buf_size);

									Print("Pan: Created character: %s\n", buf);

									break;

								default:										
									Print("Pan: Failed to create character (%d)\n", result);

									break;
							}

							break;

						case MATCH_RESPONSEDELETECHAR:
							packet->GetBytes(reinterpret_cast<unsigned char *>(&result), sizeof(result));

							switch(result)
							{	
								case RESPONSE_CHARACTER_DOESNT_EXIST:							
									Print("Pan: Failed to delete character (character was not found)\n");

									break;

								case RESPONSE_CHARACTER_IN_CLAN_OR_CASH_ITEMS:							
									Print("Pan: Failed to delete character (character is in a clan or has cash items)\n");

									break;

								case RESPONSE_OK:								
									Print("Pan: Deleted character\n");

									break;

								default:										
									Print("Pan: Failed to delete character (%d)\n", result);

									break;
							}

							break;

						case MATCH_RESPONSELOGIN:
							packet->GetBytes(reinterpret_cast<unsigned char *>(&result), sizeof(result));

							switch(result)
							{
								case RESPONSE_ACCOUNT_IN_USE:
									Print("Pan: Failed to login (account is already logged in)\n");

									break;

								case RESPONSE_AUTHENTICATION_FAILED:
									Print("Pan: Failed to login (authentication failed)\n");

									break;

								case RESPONSE_BANNED:
									Print("Pan: Failed to login (account is banned)\n");

									break;

								case RESPONSE_INVALID_PASSWORD:
									Print("Pan: Failed to login (invalid password)\n");

									break;

								case RESPONSE_INVALID_VERSION:
									Print("Pan: Failed to login (invalid version)\n");

									break;

								case RESPONSE_OK:
									unsigned short server_name_size;

									packet->GetBytes(reinterpret_cast<unsigned char *>(&server_name_size), sizeof(server_name_size));

									char server_name[SIZE_1K];

									memset(server_name, 0, sizeof(server_name));
									packet->GetBytes(reinterpret_cast<unsigned char *>(server_name), server_name_size);
									SetServerName(server_name);
									Print("Pan: Logged into server: %s\n", GetServerName());

									char type;

									packet->GetBytes(reinterpret_cast<unsigned char *>(&type), sizeof(type));
									SetServerType(type);

									char buf[SIZE_1K];
									memset(buf, 0, sizeof(buf));
						
									packet->GetBytes(reinterpret_cast<unsigned char *>(&server_name_size), sizeof(server_name_size));
									packet->GetBytes(reinterpret_cast<unsigned char *>(buf), server_name_size);

									unsigned char ugrade;

									packet->GetBytes(&ugrade, sizeof(ugrade));
									SetPlayerUGrade(ugrade);

									unsigned char pgrade;

									packet->GetBytes(&pgrade, sizeof(pgrade));
									SetPlayerPGrade(pgrade);

									unsigned long id;

									packet->GetBytes(reinterpret_cast<unsigned char *>(&id), sizeof(id));
									packet->GetBytes(reinterpret_cast<unsigned char *>(&id), sizeof(id));

									SetPlayerID(id);
									SetLoggedIn(true);

									break;

								case RESPONSE_SERVER_IS_FULL:
									Print("Pan: Failed to login (server is full)\n");

									break;

								default:	
									Print("Pan: Failed to login (%d)\n", result);

									break;
							}

							break;

						case MATCH_RESPONSESELECTCHAR:
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));				
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));	
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));	
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));	

							memset(name, 0, sizeof(name));

							for(int i = 0;; i++)
							{
								packet->GetBytes(reinterpret_cast<unsigned char *>(&name[i]), sizeof(char));

								if(!name[i])
									break;
							}

							padding = new char[31 - strlen(name)];

							memset(padding, 0, sizeof(padding));
							packet->GetBytes(reinterpret_cast<unsigned char *>(padding), 31 - strlen(name));

							delete padding;

							Print("Pan: Now using character: %s\n", name);

							char clan_name[SIZE_1K];
							memset(clan_name, 0, sizeof(clan_name));

							for(int i = 0;; i++)
							{
								packet->GetBytes(reinterpret_cast<unsigned char *>(&clan_name[i]), sizeof(char));

								if(!clan_name[i])
									break;
							}

							padding = new char[15 - strlen(clan_name)];

							memset(padding, 0, sizeof(padding));
							packet->GetBytes(reinterpret_cast<unsigned char *>(padding), 15 - strlen(clan_name));

							delete padding;

							SetCharacterClanName(clan_name);
							unsigned int clan_grade;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&clan_grade), sizeof(clan_grade));	
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(unsigned short));	

							SetCharacterClanGrade(clan_grade);
							unsigned char index;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&index), sizeof(index));	
							SetCharacterIndex(index);

							unsigned char level;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&level), sizeof(level));	
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(unsigned char));	

							SetCharacterLevel(level);
							unsigned char sex;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&sex), sizeof(sex));
							SetCharacterSex(sex);

							unsigned char hair;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&hair), sizeof(hair));
							SetCharacterHair(hair);

							unsigned char face;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&face), sizeof(face));
							SetCharacterFace(face);

							unsigned int xp;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&xp), sizeof(xp));
							SetCharacterXP(xp);

							unsigned int bp;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&bp), sizeof(bp));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));	
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));	
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(junk));

							SetCharacterBP(bp);
							unsigned short max_hp;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&max_hp), sizeof(max_hp));
							SetCharacterMaxHP(max_hp);

							unsigned short max_ap;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&max_ap), sizeof(max_ap));
							SetCharacterMaxAP(max_ap);

							break;

						case MATCH_WHISPER:
							memset(name, 0, sizeof(name));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&name_size), sizeof(name_size));
							packet->GetBytes(reinterpret_cast<unsigned char *>(name), name_size);

							memset(message, 0, sizeof(message));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&message_size), sizeof(message_size));							
							packet->GetBytes(reinterpret_cast<unsigned char *>(message), message_size);

							memset(message, 0, sizeof(message));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&message_size), sizeof(message_size));
							packet->GetBytes(reinterpret_cast<unsigned char *>(&junk), sizeof(char));
							packet->GetBytes(reinterpret_cast<unsigned char *>(message), message_size - 1);

							Print("Pan: [Whisper] <%s> %s\n", name, message);

							break;

						case MATCHSERVER_RESPONSERECOMMENDEDCHANNEL:					
							break;

						case NET_PING:
							unsigned int time;

							packet->GetBytes(reinterpret_cast<unsigned char *>(&time), sizeof(time));
							Pong(time);
						
							break;

						case STAGE_LIST:
							break;

						default:
							Print("Pan: Unknown packet: 0x%X\n", packet->GetCommandID());

							break;
					}
				}

				for(int i = 0; i < (int)plugin_count; i++)
				{
					if(GetProcAddress(reinterpret_cast<HMODULE>(plugins[i]), "Callback"))
					{
						packet->ResetIndex();
						callback = reinterpret_cast<Callback>(GetProcAddress(reinterpret_cast<HMODULE>(plugins[i]), "Callback"));
						callback(packet);
					}
				}

				delete packet;
			}
		}

		delete match;
		delete ws2;
	}
	else
		Print("Pan: Could not open file: pan.ini\n");

	return 0;
}